﻿using System.Collections.Generic;
using System.Xml;
using Windows.Foundation;
using Windows.UI.Xaml.Controls;
using LinkPad.Handlers;

namespace LinkPad.Shapes
{
    public class ShapeManager
    {
        #region Fields

        private readonly HashSet<IManagedShape> _shapes = new HashSet<IManagedShape>();

        private bool _boundariesNeedUpdate = true;
        private bool _boundariesReady;
        private Rect _boundaries;
        private TextCreator _textCreator;

        #endregion

        #region Constructors

        public ShapeManager(Canvas canvas)
        {
            Canvas = canvas;

            // NOTE: the dimensions info may have not yet been transferred to actual dimension parameters
            InitialWidth = Canvas.Width;
            InitialHeight = Canvas.Height;

            ViewLeft = 0;
            ViewTop = 0;
            ViewWidth = InitialWidth;
            ViewHeight = InitialHeight;

            ScaleX = 1;
            ScaleY = 1;
            OffsetX = 0;
            OffsetY = 0;
        }

        #endregion

        #region Properties

        public Canvas Canvas { get; private set; }

        public HashSet<IManagedShape> Shapes
        {
            get { return _shapes; }
        }

        public double InitialWidth { get; private set; }
        public double InitialHeight { get; private set; }

        public double ViewLeft { get; private set; }
        public double ViewTop { get; private set; }
        public double ViewWidth { get; private set; }
        public double ViewHeight { get; private set; }

        public double ScaleX { get; private set; }
        public double ScaleY { get; private set; }
        public double OffsetX { get; private set; }
        public double OffsetY { get; private set; }

        public Rect Boundaries
        {
            get
            {
                UpdateBoundaries();
                return _boundaries;
            }
        }

        public bool BoundariesReady
        {
            get
            {
                UpdateBoundaries();
                return _boundariesReady;
            }
        }

        #endregion

        #region Methods

        public void SetTextCreator(TextCreator textCreator)
        {
            _textCreator = textCreator;
        }

        public void AddShape(IManagedShape shape)
        {
            _shapes.Add(shape);
            shape.AddToCanvas();
        }

        public void RemoveShape(IManagedShape shape)
        {
            _shapes.Remove(shape);
            shape.RemoveFromCanvas();
        }

        /// <summary>
        ///  Sends the specified shape to the very top in the display order
        /// </summary>
        /// <param name="shape">The shape to send</param>
        public void ShapeToTop(IManagedShape shape)
        {
            shape.RemoveFromCanvas();
            shape.AddToCanvas();
        }

        /// <summary>
        ///  Sends the specified shape to the very bottom in the display order
        /// </summary>
        /// <param name="shape">The shape to send</param>
        public void ShapeToBottom(IManagedShape shape)
        {
            shape.RemoveFromCanvas();
            shape.InsertToCanvas(0);
        }

        public void Clear()
        {
            foreach (var shape in _shapes)
            {
                shape.RemoveFromCanvas();
            }
            _shapes.Clear();
        }

        public double GetAbsoluteLocationX(double x)
        {
            var canvasWidth = Canvas.ActualWidth;
            var rx = x/canvasWidth;
            return (ViewLeft + rx*ViewWidth);
        }

        public double GetAbsoluteLocationY(double y)
        {
            var canvasHeight = Canvas.ActualHeight;
            var ry = y/canvasHeight;
            return (ViewTop + ry*ViewHeight);
        }

        public double GetAbsoluteWidth(double w)
        {
            var canvasWidth = Canvas.ActualWidth;
            return w*ViewWidth/canvasWidth;
        }

        public double GetAbsoluteHeight(double h)
        {
            var canvasHeight = Canvas.ActualHeight;
            return h*ViewHeight/canvasHeight;
        }

        public void ViewAt(Rect rect)
        {
            ViewAt(rect.Left, rect.Top, rect.Width, rect.Height);
        }

        public void ViewAt(double left, double top, double width, double height)
        {
            var canvasWidth = Canvas.ActualWidth;
            var canvasHeight = Canvas.ActualHeight;

            ViewLeft = left;
            ViewTop = top;
            ViewWidth = width;
            ViewHeight = height;

            OffsetX = -left*canvasWidth/width;
            OffsetY = -top*canvasHeight/height;
            ScaleX = canvasWidth/width;
            ScaleY = canvasHeight/height;

            foreach (var shape in _shapes)
            {
                shape.Transform();
            }
        }

        public void ViewAt(double left, double top)
        {
            ViewAt(left, top, ViewWidth, ViewHeight);
        }

        public void ShapeChanged(IManagedShape shape)
        {
            _boundariesNeedUpdate = true;
        }

        /// <summary>
        ///  Saves shapes to the specified stream
        /// </summary>
        /// <param name="xmlWriter">The XML writer to write the shapes to</param>
        public void WriteToXml(XmlWriter xmlWriter)
        {
            xmlWriter.WriteStartElement("Shapes");
            foreach (var shape in _shapes)
            {
                shape.WriteToXml(xmlWriter);
            }
            xmlWriter.WriteEndElement();
            xmlWriter.Flush();
        }

        /// <summary>
        ///  Loads shapes from the specified stream
        /// </summary>
        /// <param name="xmlReader">The XML reader to read the shapes from</param>
        public void ReadFromXml(XmlReader xmlReader)
        {
            var finished = false;
            Clear();
            while (xmlReader.Read() && !finished)
            {
                var nodeType = xmlReader.NodeType;
                var name = xmlReader.Name;
                switch (nodeType)
                {
                    case XmlNodeType.Element:
                        IManagedShape shape = null;
                        switch (name)
                        {
                            case "Text":
                                shape = new ManagedText(this);
                                if (_textCreator != null)
                                {
                                    _textCreator.AttachManagedText((ManagedText)shape);
                                }
                                break;
                            case "ArrowedLink":
                                shape = new ArrowedLink(this);
                                break;
                            case "Line":
                                shape = new ManagedLine(this);
                                break;
                        }
                        if (shape != null && shape.ReadFromXml(xmlReader))
                        {
                            AddShape(shape);
                        }
                        break;
                    case XmlNodeType.EndElement:
                        if (name == "Shapes")
                        {
                            finished = true;
                        }
                        break;
                }
            }
        }

        private void UpdateBoundaries()
        {
            if (!_boundariesNeedUpdate)
            {
                return;
            }
            var firstTimeThrough = true;
            _boundariesReady = true;
            foreach (var shape in _shapes)
            {
                if (!shape.RealBoundingBoxReady)
                {
                    _boundariesReady = false;
                }
                var bb = shape.RealBoundingBox;
                if (firstTimeThrough)
                {
                    _boundaries = bb;
                    firstTimeThrough = false;
                }
                else
                {
                    _boundaries.Union(bb);
                }
            }
            _boundariesNeedUpdate = false;
        }

        #endregion
    }
}

